#include "Shell.h"

Shell::Shell(char* en[]){
    envr = en;
    forLoopMode = false;

}

int Shell::runShell(){
    //Gets user name and host name
    char cmdStr[256], *user, hostname[256];

    memset(hostname, 0, sizeof(hostname));

    gethostname(hostname, sizeof(hostname));
    user = (char *)getenv("USER");

    if(user == NULL)
    {
        strcpy(user, "unknown");
    }

    /*
    Initialization
    */

    Environment ev = Environment();

    Executor exe = Executor();


    ev.loadConfig();

    Parser p = Parser(&ev);

    std::list<struct threadInfo*> executedProcs;


    while(1){


        //Checks if it is in for loop mode
        if(forLoopMode){
            printf(">");
            memset(cmdStr, 0, sizeof(cmdStr));
            fgets(cmdStr, sizeof(cmdStr), stdin);
            /*
            Exits out of for loop mode
            */
            if(strncmp("forend", cmdStr, 6) == 0){
                //Executes the for loop
                int first = firstValue, last = lastValue;

                if(first < last)
                    last++;
                else
                    last--;
                while(first!=last){
                    std::stringstream out;
                    out << first;
                    ev.setValue(varName, out.str());
                    for(int i = 0; i < forLoopCommands.size(); i++){

                        processInput(p, exe, &executedProcs, (char*)forLoopCommands[i].c_str());

                    }
                    if(first < last)
                        first++;
                    else
                        first--;
                }



                forLoopMode = false;
                forLoopCommands.clear();
            }
            else
                forLoopCommands.push_back(std::string(cmdStr));

        }//End of For loop mode
        else{

            printf("%s@%s: ", user, hostname);


            memset(cmdStr, 0, sizeof(cmdStr));


            //Grabs user input.
            fgets(cmdStr, sizeof(cmdStr), stdin);

            if( strncmp("quit", cmdStr, 4) == 0 ){
                break;
            }
            else if(strncmp("for ", cmdStr, 4) == 0){ //Handles for loop operation
                std::string input = std::string(cmdStr);
                processForLoop(input);
                if(!forLoopMode){
                    printf("Usage: for n in {a..b}\n\nWhere n is a variable and a and b are integer values\n\n");
                }
            }
            else if(strncmp("$?", cmdStr, 2) == 0){ //Gets exit status
                int processNumber = atoi((cmdStr+2));

                std::list<struct threadInfo*>::reverse_iterator rit = executedProcs.rbegin();
                while(processNumber>0 && rit != executedProcs.rend()){
                    processNumber--;
                    if(processNumber==0){
                        int status = (*(*rit)).exitStatus;

                        printf("%d\n", status);
                    }
                    else{
                        rit++;
                    }
                }
            }
            else if(strncmp("cd ", cmdStr, 3) == 0){
                std::string path = std::string(cmdStr+3);
                path = path.substr(0, path.size()-1);

                if(path.at(0)!='/'){ //relative pathing
                    char buf[50];
                    getcwd(buf, 50);
                    path = std::string(buf) + "/" +path;
                }



                char* cPath = (char*) malloc(path.size()*sizeof(char));
                for(int i = 0; i < path.size(); i++){
                    *(cPath+i) = path.at(i);
                }
                *(cPath+path.size()) = '\0';

                if(chdir(cPath)==-1){
                    printf("Invalid path\n");
                }
            }
            else if(strncmp("$&", cmdStr, 2) == 0){  //Get Background processes
                for(std::list<threadInfo*>::iterator it = executedProcs.begin();
                    it != executedProcs.end(); it++){
                    threadInfo th = **it;
                    if(th.background){
                        printf("Process ID %d:  %s", th.procID, **(th.args));
                        if(th.isDone){
                            printf("     Complete");
                        }
                        else
                            printf("     Running");

                        printf("\n");
                    }
                }
            }
            else if(strncmp("fg ", cmdStr, 3) == 0){//switch background to foreground
               int procID = atoi(cmdStr+3);
               for(std::list<threadInfo*>::iterator it = executedProcs.begin();
                   it != executedProcs.end(); it++){
                   threadInfo th = **it;
                   if(th.background && th.procID==procID && !th.isDone){
                       //th.background=false;
                       int x = 0;
                       while(1){
                           threadInfo temp = **it;
                           if(temp.isDone == false || x < temp.output.size()){
                               if(temp.output[x] != NULL){
                                   std::cout << temp.output[x];
                                   x++;
                               }
                           } else {
                               break;
                           }

                       }
                   }

               }
           }
            else
                processInput(p, exe, &executedProcs, cmdStr);
        }

    }

    return 0;
}

void Shell::processInput(Parser p, Executor exe, std::list<struct threadInfo*> *executedProcs, char * cmdStr){
    int commandCode = p.parseInput(cmdStr);
    std::list<Command> commandList = p.getCmdList();
    if(commandCode == p.COMMAND){  //This handles the single/background/pipelined commands
        threadInfo* currentThreadInfo = new threadInfo;
        //To set fileIN or OUT, just go like this
        //currenthreadInfo.fileIN = "/some/file";
        //currentThreadInfo.fileOUT = "/someFile/ out/"
        //No error checking is done.
        //

        (*currentThreadInfo).args = p.getArgList();
        if(p.getSTDOUT().compare("")!=0)
            (*currentThreadInfo).fileOUT = (char*)p.getSTDOUT().c_str();
        else
            (*currentThreadInfo).fileOUT = NULL;

        if(p.getSTDIN().compare("")!=0)
            (*currentThreadInfo).fileIN = (char*)p.getSTDIN().c_str();
        else
            (*currentThreadInfo).fileIN = NULL;

        if(p.getSTDERR().compare("")!=0)
                (*currentThreadInfo).fileERROR = (char*)p.getSTDERR().c_str();
        else
                (*currentThreadInfo).fileERROR = NULL;


        (*executedProcs).push_back(currentThreadInfo);


        exe.runThread((*currentThreadInfo).args, p.isBackgroundProcess(), envr, currentThreadInfo);


    }
    else if(commandCode==p.CONDITIONAL){  // This does the conditional commands

        std::vector<char> conditionalList = p.getConditionals();
        std::vector<char>::iterator conditionalIt = conditionalList.begin();
        std::vector<int> conditionalStatus; //Stack



        std::list<Command> comList = p.getCmdList();


        for(std::list<Command>::iterator comListIt = comList.begin(); comListIt!=comList.end(); comListIt++){
            threadInfo* currentThreadInfo = new threadInfo;
            char*** args = (char***) malloc(2*sizeof(char**));

            *args = (*comListIt).getArgs();
            *(args+1) = NULL;



            (*currentThreadInfo).args = args;



            if(p.getSTDOUT().compare("")!=0)
                (*currentThreadInfo).fileOUT = (char*)p.getSTDOUT().c_str();
            else
                (*currentThreadInfo).fileOUT = NULL;

            if(p.getSTDIN().compare("")!=0)
                (*currentThreadInfo).fileIN = (char*)p.getSTDIN().c_str();
            else
                (*currentThreadInfo).fileIN = NULL;

            if(p.getSTDERR().compare("")!=0)
                (*currentThreadInfo).fileERROR = (char*)p.getSTDERR().c_str();
            else
                (*currentThreadInfo).fileERROR = NULL;

            (*executedProcs).push_back(currentThreadInfo);

            exe.runThread((*currentThreadInfo).args, false, envr, currentThreadInfo);



            int status = (*currentThreadInfo).exitStatus;

            if(conditionalIt!=p.getConditionals().end() && *conditionalIt=='!'){
                        status = !status;
                        conditionalIt++;
            }


            conditionalStatus.push_back(status);

            if(conditionalStatus.size()==2){  //Evaluate the conditional commands
                int operand2 = conditionalStatus.back();
                conditionalStatus.pop_back();
                int operand1 = conditionalStatus.back();
                conditionalStatus.pop_back();

                if(conditionalIt!=p.getConditionals().end() && *conditionalIt=='&'){
                    conditionalIt++;
                    if(conditionalIt!=p.getConditionals().end() && *conditionalIt=='!'){
                        operand2 = !operand2;
                        conditionalIt++;
                    }
                    conditionalStatus.push_back(operand1 && operand2);

                }
                else if(conditionalIt!=p.getConditionals().end() && *conditionalIt=='|'){
                    conditionalIt++;
                    if(conditionalIt!=p.getConditionals().end() && *conditionalIt=='!'){
                        operand2 = !operand2;
                        conditionalIt++;
                    }
                    conditionalStatus.push_back(operand1 || operand2);
                }
                if(!conditionalStatus.back()){  //Exit Conditional statement if this returns false
                    break;
                }
            }
            else{ // This looks ahead to see if the next conditional is a && and it only evaluated 1 command.  If the current status is 0 then it will exit right away
                if(conditionalIt!=p.getConditionals().end() && *conditionalIt=='&' && !conditionalStatus.back())
                    break;
            }

        }
        printf("Exit status: %d\n", conditionalStatus.back());
        p.reset();
    }
    if((*executedProcs).size() >= 51){
        (*executedProcs).pop_front();
    }


    p.reset();
}


void Shell::resetMode(){
    forLoopMode = false;
    forLoopCommands.clear();
}

void Shell::processForLoop(std::string input){
    if(input.size() <= 4){
        forLoopMode=false;
        return ;
    }


    char reader;
    varName.clear();
    int index = 4;
    reader = input.at(index);
    while(isalnum(reader)){
        varName += reader;
        index++;
        if(index >= input.size())
            return ;
        else
            reader = input.at(index);
    }

    if(input.compare(index, 4, " in ")!=0){
        return ;
    }

    index += 4;

    if(index < input.size() && input.at(index)=='{'){
        index++;
        if(index >= input.size()){
            return ;
        }
        firstValue = atoi(input.substr(index, std::string::npos).c_str());

        //Looks for the next number after the first number

        //This skips the first number
        while(index < input.size() && isdigit(reader = input.at(index))  ){
            index++;
        }
        //This skips the ".." inbetween the values
        while(index < input.size() && !isdigit(reader = input.at(index))  ){
            index++;
        }

        //Exits with an error
        if(index >= input.size()){
            return;
        }

        lastValue = atoi(input.substr(index, std::string::npos).c_str());

    }
    else{
        return ;
    }

    forLoopMode = true;


    return;
}
